﻿using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using GiveAQuiz.Data;
using GiveAQuiz.Web.Authentication;
using GiveAQuiz.Web.Membership;
using GiveAQuiz.Web.Messenger;
using GiveAQuiz.Web.Models;
using GiveAQuizSite.Controllers;
using Moq;
using MvcContrib.TestHelper;
using NUnit.Framework;

namespace GiveAQuizSite.Tests.Controllers
{
  [TestFixture]
  public class AccountControllerTest : AuthenticationControllerTest
  {
    [Test]
    public void ChangePassword_Get_should_return_ChangePassword_view()
    {
      // Arrange
      AccountController controller = GetAccountController();

      // Act
      ViewResult result = (ViewResult)controller.ChangePassword();

      // Assert
      result.AssertViewRendered();
      Assert.AreEqual(controller.MembershipService.MinPasswordLength, result.ViewData["PasswordLength"]);
    }

    [Test]
    public void ChangePassword_Post_should_redirect_to_ChangePasswordSuccess_if_successful()
    {
      // Arrange
      AccountController controller = GetAccountController();

      var model = new ChangePasswordViewModel
      {
        CurrentPassword = "oldPass",
        NewPassword = "newPass",
        RepeatPassword = "newPass"
      };

      // Act
      RedirectToRouteResult result = (RedirectToRouteResult)controller.ChangePassword(model);

      // Assert
      result.AssertActionRedirect().ToAction("ChangePasswordSuccess");
    }

    [Test]
    public void ChangePassword_Post_should_return_view_if_ModelState_IsValid_is_false()
    {
      // Arrange
      AccountController controller = GetAccountController();

      // add an error
      controller.ModelState.AddModelError("CurrentPassword", "Dummy");

      var model = new ChangePasswordViewModel
      {
        CurrentPassword = String.Empty,
        NewPassword = "newPassword",
        RepeatPassword = "newPassword"
      };

      // Act
      var result = controller.ChangePassword(model) as ViewResult;

      // Assert
      result.AssertViewRendered();
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
      Assert.AreEqual(controller.MembershipService.MinPasswordLength, result.ViewData["PasswordLength"]);
    }

    [Test]
    public void ChangePassword_Post_should_return_current_view_if_MembershipService_ChangePassword_returns_false()
    {
      // Arrange
      var mockMembershipSvc = new Mock<IMembershipService>();
      // return false on ChangePassword()
      mockMembershipSvc
        .Setup(m => m.ChangePassword(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
        .Returns(false)
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipSvc.Object);

      var model = new ChangePasswordViewModel
      {
        CurrentPassword = "oldPass",
        NewPassword = "badPass",
        RepeatPassword = "badPass"
      };

      // Act
      ViewResult result = (ViewResult)controller.ChangePassword(model);

      // Assert
      mockMembershipSvc.VerifyAll(); // make sure the ChangePassword was called
      result.AssertViewRendered();
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
    }

    [Test]
    public void ChangePasswordSuccess_should_return_ChangePasswordSuccess_view()
    {
      // Arrange
      AccountController controller = GetAccountController();

      // Act
      ViewResult result = (ViewResult)controller.ChangePasswordSuccess();

      // Assert
      result.AssertViewRendered();
    }

    [Test]
    public void Constructor_should_set_properties()
    {
      // Arrange
      IQuizRepository repository = new Mock<IQuizRepository>().Object;
      IAccountMessenger acctMessenger = new Mock<IAccountMessenger>().Object;
      IFormsAuthentication formsAuth = new Mock<IFormsAuthentication>().Object;
      IMembershipService membershipService = new AccountMembershipService(Membership.Provider);

      // Act
      AccountController controller = new AccountController(repository, formsAuth, membershipService, acctMessenger);

      // Assert
      Assert.AreSame(repository, controller.Repository, "Repository property does not match");
      Assert.AreSame(formsAuth, controller.FormsAuth, "FormsAuth property did not match.");
      Assert.AreSame(membershipService, controller.MembershipService, "MembershipService property did not match.");
      Assert.AreSame(acctMessenger, controller.AccountMessenger, "AccountMessenger property did not match.");
    }

    [Test]
    public void LogOn_Get_should_return_view()
    {
      // Arrange
      AccountController controller = GetAccountController();

      // Act
      ViewResult result = (ViewResult)controller.LogOn(String.Empty);

      // Assert
      result.AssertViewRendered();
    }

    [Test]
    public void LogOn_Post_should_redirect_to_Home_if_login_is_sucessful_but_no_return_url_is_provided()
    {
      // Arrange
      AccountController controller = GetAccountController();

      var viewModel =
        new LogOnViewModel
          {
            UserName = "someUser",
            Password = "goodPass",
            IsRemembered = true
          };
      // Act
      RedirectToRouteResult result = (RedirectToRouteResult)controller.LogOn(viewModel);

      // Assert
      result.AssertActionRedirect().ToAction("Index").ToController("Home");
    }

    [Test]
    public void LogOn_Post_should_redirect_to_return_url_if_login_is_sucessful_but_no_return_url_is_provided()
    {
      // Arrange
      AccountController controller = GetAccountController();

      var viewModel =
        new LogOnViewModel
        {
          UserName = "someUser",
          Password = "goodPass",
          IsRemembered = false, 
          ReturnUrl = "someUrl"
        };
      // Act
      RedirectResult result = (RedirectResult)controller.LogOn(viewModel);

      // Assert
      Assert.AreEqual("someUrl", result.Url);
    }

    [Test]
    public void LogOn_Post_should_return_current_view_if_ModelState_IsValid_is_false()
    {
      // Arrange
      AccountController controller = GetAccountController();
      controller.ModelState.AddModelError("_FORM", "You must specify a password");

      var viewModel =
        new LogOnViewModel
        {
          UserName = "username",
          Password = "",
          IsRemembered = true,
          ReturnUrl = null
        };

      // Act
      ViewResult result = (ViewResult)controller.LogOn(viewModel);

      // Assert
      result.AssertViewRendered();
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
    }

    [Test]
    public void LogOn_Post_should_return_current_view_if_MembershipService_ValidateUser_is_false()
    {
      // Arrange
      var mockMembershipService = new Mock<IMembershipService>();
      mockMembershipService
        .Setup(m => m.ValidateUser("username", String.Empty))
        .Returns(false)
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var viewModel =
        new LogOnViewModel
        {
          UserName = "username",
          Password = "",
          IsRemembered = true,
          ReturnUrl = null
        };

      // Act
      ViewResult result = (ViewResult)controller.LogOn(viewModel);

      // Assert
      mockMembershipService.VerifyAll();
      result.AssertViewRendered();
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
    }    

    [Test]
    public void LogOff_should_call_FormsAuthenticationService_SignOut()
    {
      // Arrange
      AccountController controller = GetAccountController();

      // Act
      RedirectToRouteResult result = (RedirectToRouteResult)controller.LogOff();

      // Assert
      MockFormsAuthentication.Verify(fa => fa.SignOut());
    }

    [Test]
    public void LogOff_should_redirect_to_Home_Index()
    {
      // Arrange
      AccountController controller = GetAccountController();

      // Act
      RedirectToRouteResult result = (RedirectToRouteResult)controller.LogOff();

      // Assert
      result.AssertActionRedirect().ToAction("Index").ToController("Home");
    }

    [Test]
    public void ResetPassword_Get_should_return_current_view()
    {
      AccountController controller = GetAccountController();

      ViewResult result = controller.ResetPassword() as ViewResult;

      result.AssertViewRendered();
    }

    [Test]
    public void ResetPassword_Post_should_return_current_view_if_ModelState_IsValid_is_false()
    {
      AccountController controller = GetAccountController();
      controller.ModelState.AddModelError("_FORM", "You must enter a username or email");

      ViewResult result = controller.ResetPassword(new ResetPasswordViewModel()) as ViewResult;

      result.AssertViewRendered().ForView("ResetPassword");
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
    }

    [Test]
    public void ResetPassword_Post_should_call_MembershipService_GetUser_if_username_is_provided()
    {
      var mockMembershipService = new Mock<IMembershipService>();
      var mockUser = CreateMockUser();

      // set up the mock for verification
      mockMembershipService
        .Setup(m => m.GetUser("foo"))
        .Returns(mockUser.Object)
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var model = new ResetPasswordViewModel { UserName = "foo" };

      ViewResult result = controller.ResetPassword(model) as ViewResult;

      // verify the call was made
      mockMembershipService.VerifyAll();
      result.AssertViewRendered().ForView("ResetPasswordSuccess");      
    }

    [Test]
    public void ResetPassword_Post_should_call_MembershipService_GetUserByEmail_if_email_is_provided()
    {
      var mockMembershipService = new Mock<IMembershipService>();
      var mockUser = CreateMockUser();

      // set up the mock for verification
      mockMembershipService
        .Setup(m => m.GetUserByEmail("foo@bar.com"))
        .Returns(mockUser.Object)
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var model = new ResetPasswordViewModel { Email = "foo@bar.com" };

      ViewResult result = controller.ResetPassword(model) as ViewResult;

      // verify the call was made
      mockMembershipService.VerifyAll();
      result.AssertViewRendered().ForView("ResetPasswordSuccess");
    }

    [Test]
    public void ResetPassword_Post_should_call_MembershipService_ResetPassword()
    {
      var mockMembershipService = new Mock<IMembershipService>();
      var mockUser = CreateMockUser();

      // set up the mock for verification
      mockMembershipService
        .Setup(m => m.GetUser(mockUser.Object.UserName))
        .Returns(mockUser.Object)
        .Verifiable();
      mockMembershipService
        .Setup(m => m.ResetPassword(mockUser.Object.UserName))
        .Returns("123456")
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var model = new ResetPasswordViewModel { UserName = mockUser.Object.UserName };

      ViewResult result = controller.ResetPassword(model) as ViewResult;

      // verify the call was made
      mockMembershipService.VerifyAll();
      result.AssertViewRendered().ForView("ResetPasswordSuccess");
    }

    [Test]
    public void ResetPassword_Post_should_call_AccountMessenger_SendPasswordResetMessage()
    {
      var mockMembershipService = new Mock<IMembershipService>();
      var mockUser = CreateMockUser();

      // set up the membership service mock for verification
      mockMembershipService
        .Setup(m => m.GetUser(mockUser.Object.UserName))
        .Returns(mockUser.Object)
        .Verifiable();
      mockMembershipService
        .Setup(m => m.ResetPassword(mockUser.Object.UserName))
        .Returns("123456")
        .Verifiable();

      // set up the account messenger mock for verification
      MockAccountMessenger
        .Setup(am => am.SendPasswordResetMessage(mockUser.Object, "123456"))
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var model = new ResetPasswordViewModel { UserName = mockUser.Object.UserName };

      ViewResult result = controller.ResetPassword(model) as ViewResult;

      // verify the call was made
      mockMembershipService.VerifyAll();
      MockAccountMessenger.VerifyAll();

      result.AssertViewRendered().ForView("ResetPasswordSuccess");
    }

    [Test]
    public void ResetPassword_Post_should_return_ResetPassword_View_if_MembershipService_GetUser_returns_null()
    {
      var mockMembershipService = new Mock<IMembershipService>();
      var mockUser = CreateMockUser();

      // set up the membership service mock for verification
      mockMembershipService
        .Setup(m => m.GetUser(mockUser.Object.UserName))
        .Returns(null as MembershipUser) // not found
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var model = new ResetPasswordViewModel { UserName = mockUser.Object.UserName };

      ViewResult result = controller.ResetPassword(model) as ViewResult;

      // verify the call was made
      mockMembershipService.VerifyAll();

      result.AssertViewRendered().ForView("ResetPassword");
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
      Assert.AreEqual("No matching User was found", result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage);
    }

    [Test]
    public void ResetPassword_Post_should_return_ResetPassword_View_if_MembershipService_GetUserByEmail_returns_null()
    {
      var mockMembershipService = new Mock<IMembershipService>();
      var mockUser = CreateMockUser();

      // set up the membership service mock for verification
      mockMembershipService
        .Setup(m => m.GetUserByEmail(mockUser.Object.Email))
        .Returns(null as MembershipUser) // not found
        .Verifiable();

      AccountController controller = GetAccountController(mockMembershipService.Object);

      var model = new ResetPasswordViewModel { Email = mockUser.Object.Email };

      ViewResult result = controller.ResetPassword(model) as ViewResult;

      // verify the call was made
      mockMembershipService.VerifyAll();

      result.AssertViewRendered().ForView("ResetPassword");
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
      Assert.AreEqual("No matching User was found", result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage);
    }

    private AccountController GetAccountController()
    {
      MembershipProvider membershipProvider = new MockMembershipProvider();
      AccountMembershipService membershipService = new AccountMembershipService(membershipProvider);
      return GetAccountController(membershipService);
    }

    private AccountController GetAccountController(IMembershipService membershipService)
    {
      AccountController controller = 
        new AccountController(MockRepository.Object, MockFormsAuthentication.Object, membershipService, MockAccountMessenger.Object);
      ControllerContext controllerContext = new ControllerContext(MockHttpContext.Object, new RouteData(), controller);
      controller.ControllerContext = controllerContext;
      return controller;
    }    
  }
}
